import React, {
  useEffect, useState, useRef, useMemo, forwardRef,
} from 'react';

import PropTypes from 'prop-types';

import classnames from 'classnames';

import { deepMerge } from '@jiaminghi/charts/lib/util/index';

import { deepClone } from '@jiaminghi/c-render/lib/plugin/util';

import { useAutoResize, co } from '@jiaminghi/data-view-react';

import './style.less';

const defaultConfig = {
  /**
   * @description Board header
   * @type {Array<String>}
   * @default header = []
   * @example header = ['column1', 'column2', 'column3']
   */
  header: [],
  /**
   * @description Board data
   * @type {Array<Array>}
   * @default data = []
   */
  data: [],
  /**
   * @description Row num
   * @type {Number}
   * @default rowNum = 5
   */
  rowNum: 5,
  /**
   * @description Header background color
   * @type {String}
   * @default headerBGC = '#00BAFF'
   */
  headerBGC: '#00BAFF',
  /**
   * @description Odd row background color
   * @type {String}
   * @default oddRowBGC = '#003B51'
   */
  oddRowBGC: '#003B51',
  /**
   * @description Even row background color
   * @type {String}
   * @default evenRowBGC = '#003B51'
   */
  evenRowBGC: '#0A2732',
  /**
   * @description Scroll wait time
   * @type {Number}
   * @default waitTime = 2000
   */
  waitTime: 2000,
  /**
   * @description Header height
   * @type {Number}
   * @default headerHeight = 35
   */
  headerHeight: 35,
  /**
   * @description Column width
   * @type {Array<Number>}
   * @default columnWidth = []
   */
  columnWidth: [],
  /**
   * @description Column align
   * @type {Array<String>}
   * @default align = []
   * @example align = ['left', 'center', 'right']
   */
  align: [],
  /**
   * @description Show index
   * @type {Boolean}
   * @default index = false
   */
  index: false,
  /**
   * @description index Header
   * @type {String}
   * @default indexHeader = '#'
   */
  indexHeader: '#',
  /**
   * @description Carousel type
   * @type {String}
   * @default carousel = 'single'
   * @example carousel = 'single' | 'page'
   */
  carousel: 'single',
  /**
   * @description Pause scroll when mouse hovered
   * @type {Boolean}
   * @default hoverPause = true
   * @example hoverPause = true | false
   */
  hoverPause: true,
};

function calcHeaderData({ header, index, indexHeader }) {
  if (!header.length) {
    return [];
  }

  header = [...header];

  if (index) header.unshift(indexHeader);

  return header;
}

function calcRows({
  data, index, headerBGC, rowNum,
}) {
  if (index) {
    data = data.map((row, i) => {
      row = [...row];

      const indexTag = `<span class="index" style="background-color: ${headerBGC};">${i
        + 1}</span>`;

      row.unshift(indexTag);

      return row;
    });
  }

  data = data.map((ceils, i) => ({ ceils, rowIndex: i }));

  const rowLength = data.length;

  if (rowLength > rowNum && rowLength < 2 * rowNum) {
    data = [...data, ...data];
  }

  return data.map((d, i) => ({ ...d, scroll: i }));
}

function calcAligns(mergedConfig, header) {
  const columnNum = header.length;

  const aligns = new Array(columnNum).fill('left');

  const { align } = mergedConfig;

  return deepMerge(aligns, align);
}

const ScrollBoard = forwardRef(({
  onClick, config = {}, className, style, onMouseOver,
}, ref) => {
  const { width, height, domRef } = useAutoResize(ref);

  const [state, setState] = useState({
    mergedConfig: null,

    header: [],

    rows: [],

    rowsShow: [],

    widths: [],

    heights: [],

    aligns: [],
  });

  const {
    mergedConfig, header, rows, widths, heights, aligns, rowsShow,
  } = state;

  const stateRef = useRef({
    ...state,
    rowsData: [],
    avgHeight: 0,
    animationIndex: 0,
  });

  Object.assign(stateRef.current, state);

  function onResize() {
    if (!mergedConfig) return;

    const widths = calcWidths(mergedConfig, stateRef.current.rowsData);

    const heights = calcHeights(mergedConfig, header);

    const data = { widths, heights };

    Object.assign(stateRef.current, data);
    setState((state) => ({ ...state, ...data }));
  }
  const [init, setInit] = useState(true);

  function calcData() {
    // const mergedConfig = deepMerge(
    //   deepClone(defaultConfig, true),
    //   config || {},
    // );
    const mergedConfig = {
      ...defaultConfig,
      ...config,
    };

    const header = calcHeaderData(mergedConfig);

    const rows = calcRows(mergedConfig);

    const widths = calcWidths(mergedConfig, stateRef.current.rowsData);

    const heights = calcHeights(mergedConfig, header);

    const aligns = calcAligns(mergedConfig, header);

    const data = {
      mergedConfig,
      header,
      rows,
      widths,
      aligns,
      heights: init ? heights : state.heights.concat(heights),
      rowsShow: init ? rows : state.rowsShow,
    };
    setInit(false);
    Object.assign(stateRef.current, data, {
      rowsData: rows,
      animationIndex: stateRef.current.animationIndex,
    });

    setState((state) => ({ ...state, ...data }));
  }

  function calcWidths({ columnWidth, header }, rowsData) {
    const usedWidth = columnWidth.reduce((all, w) => all + w, 0);

    let columnNum = 0;
    if (rowsData[0]) {
      columnNum = rowsData[0].ceils.length;
    } else if (header.length) {
      columnNum = header.length;
    }

    const avgWidth = (width - usedWidth) / (columnNum - columnWidth.length);

    const widths = new Array(columnNum).fill(avgWidth);

    return deepMerge(widths, columnWidth);
  }

  function calcHeights({ headerHeight, rowNum, data }, header) {
    let allHeight = height;

    if (header.length) allHeight -= headerHeight;

    const avgHeight = allHeight / rowNum;

    Object.assign(stateRef.current, { avgHeight });

    return new Array(data.length).fill(avgHeight);
  }

  function* animation(start = false) {
    let {
      avgHeight,
      animationIndex,
      mergedConfig: { waitTime, carousel, rowNum },
      rowsData,
    } = stateRef.current;

    const rowLength = rowsData.length;

    if (start) yield new Promise((resolve) => setTimeout(resolve, waitTime));

    const animationNum = carousel === 'single' ? 1 : rowNum;

    let rows = rowsData.slice(animationIndex);
    rows.push(...rowsData.slice(0, animationIndex));
    rows = rows.slice(0, carousel === 'page' ? rowNum * 2 : rowNum + 1);

    const heights = new Array(rowLength).fill(avgHeight);
    setState((state) => ({
      ...state, rows, heights, rowsShow: rows,
    }));

    yield new Promise((resolve) => setTimeout(resolve, 300));

    animationIndex += animationNum;

    const back = animationIndex - rowLength;
    if (back >= 0) animationIndex = back;

    const newHeights = [...heights];
    newHeights.splice(0, animationNum, ...new Array(animationNum).fill(0));

    Object.assign(stateRef.current, { animationIndex });
    setState((state) => ({ ...state, heights: newHeights }));
  }

  function emitEvent(handle, ri, ci, row, ceil) {
    const { ceils, rowIndex } = row;

    handle && handle({
      row: ceils, ceil, rowIndex, columnIndex: ci,
    });
  }

  function handleHover(enter, ri, ci, row, ceil) {
    if (enter) emitEvent(onMouseOver, ri, ci, row, ceil);

    if (!mergedConfig.hoverPause) return;

    const { pause, resume } = task.current;

    enter && pause && resume ? pause() : resume && resume();
  }

  // updateRows(rows, animationIndex) {
  //   const { mergedConfig, animationHandler, animation } = this
  //   this.mergedConfig = {
  //     ...mergedConfig,
  //     data: [...rows]
  //   }
  //   this.needCalc = true
  //   if (typeof animationIndex === 'number') this.animationIndex = animationIndex
  //   if (!animationHandler) animation(true)
  // }

  const getBackgroundColor = (rowIndex) => mergedConfig[rowIndex % 2 === 0 ? 'evenRowBGC' : 'oddRowBGC'];

  const task = useRef({});

  useEffect(() => {
    calcData();

    let start = true;

    function* loop() {
      while (true) {
        yield* animation(start);

        start = false;

        const { waitTime } = stateRef.current.mergedConfig;

        yield new Promise((resolve) => setTimeout(resolve, waitTime - 300));
      }
    }

    const {
      mergedConfig: { rowNum },
      rows: rowsData,
    } = stateRef.current;

    const rowLength = rowsData.length;

    if (rowNum >= rowLength) {
      setState((prestate) => ({
        ...prestate, rowsShow: state.rows,
      }));
      return;
    }

    task.current = co(loop);

    return task.current.end;
  }, [config, domRef.current]);

  useEffect(onResize, [width, height, domRef.current]);

  const classNames = useMemo(() => classnames('dv-scroll-board', className), [
    className,
  ]);

  return (
    <div className={classNames} style={style} ref={domRef}>
      {!!header.length && !!mergedConfig && (
        <div
          className="header"
          style={{ backgroundColor: `${mergedConfig.headerBGC}` }}
        >
          {header.map((headerItem, i) => (
            <div
              className="header-item"
              key={`${headerItem}-${i}`}
              style={{
                height: `${mergedConfig.headerHeight}px`,
                lineHeight: `${mergedConfig.headerHeight}px`,
                width: `${widths[i]}px`,
              }}
              align={aligns[i]}
              dangerouslySetInnerHTML={{ __html: headerItem }}
            />
          ))}
        </div>
      )}

      {!!mergedConfig && (
        <div
          className="rows"
          style={{
            height: `${height
              - (header.length ? mergedConfig.headerHeight : 0)}px`,
          }}
        >
          {rowsShow.map((row, ri) => (
            <div
              className="row-item"
              key={`${row.toString()}-${row.scroll}`}
              style={{
                height: `${heights[ri]}px`,
                lineHeight: `${heights[ri]}px`,
                backgroundColor: `${getBackgroundColor(row.rowIndex)}`,
              }}
            >
              {row.ceils.map((ceil, ci) => {
                if (typeof (ceil) === 'string') {
                  return (
                    <div
                      className="ceil"
                      key={`${ceil}-${ri}-${ci}`}
                      style={{ width: `${widths[ci]}px` }}
                      align={aligns[ci]}
                      dangerouslySetInnerHTML={{ __html: ceil }}
                      onClick={() => emitEvent(onClick, ri, ci, row, ceil)}
                      onMouseEnter={() => handleHover(true, ri, ci, row, ceil)}
                      onMouseLeave={() => handleHover(false)}
                    />
                  );
                }
                return (
                  <div
                    className="ceil"
                    style={{ width: `${widths[ci]}px` }}
                    align={aligns[ci]}
                    key={`${ri}-${ci}`}
                    onMouseEnter={() => handleHover(true, ri, ci, row, ceil)}
                    onMouseLeave={() => handleHover(false)}
                  >
                    {ceil}
                  </div>
                );
              })}
            </div>
          ))}
        </div>
      )}
    </div>
  );
});

ScrollBoard.propTypes = {
  config: PropTypes.object,
  onClick: PropTypes.func,
  onMouseOver: PropTypes.func,
  className: PropTypes.string,
  style: PropTypes.object,
};

export default ScrollBoard;
